/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.aplikator.server.persistence.empiredb.oracle;

import java.sql.Connection;

import org.apache.empire.db.DBCmdType;
import org.apache.empire.db.DBColumn;
import org.apache.empire.db.DBCommand;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.db.DBDriverFeature;
import org.apache.empire.db.DBObject;
import org.apache.empire.db.DBReader;
import org.apache.empire.db.DBSQLScript;
import org.apache.empire.db.DBTable;
import org.apache.empire.db.DBView;
import org.apache.empire.db.oracle.DBDatabaseDriverOracle;
import org.apache.empire.exceptions.InvalidArgumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class provides support for the Oracle database system.<br>
 * Oracle Version 9 or higher is required.
 */
public class EmpireDriverOracle extends DBDatabaseDriverOracle {
	private static final long serialVersionUID = 1L;
	private static final Logger log = LoggerFactory
			.getLogger(EmpireDriverOracle.class);
	private EmpireDDLGeneratorOracle ddlGenerator = null; // lazy creation

	private EmpireDDLGeneratorOracle getDDLGeneratorOracle() {
		if (ddlGenerator == null)
			ddlGenerator = new EmpireDDLGeneratorOracle(this);
		return ddlGenerator;
	}

	public void getDDLScript(DBCmdType type, DBObject dbo, DBSQLScript script,
			Connection conn) {
		getDDLGeneratorOracle();
		// forward request
		ddlGenerator.getDDLScript(type, dbo, script);
	}
	
	 @Override
	    public boolean isSupported(DBDriverFeature type)
	    {
	        switch (type)
	        {   // return support info 
	            case CREATE_SCHEMA: 	return true;
	            case SEQUENCES:     	return true;
	            case QUERY_LIMIT_ROWS:  return true;
	            case QUERY_SKIP_ROWS:   return true;
	            default:
	                // All other features are not supported by default
	                return false;
	        }
	    }

	/**
	 * Checks whether the database definition matches the real database
	 * structure.
	 */
	public void checkDatabase(DBDatabase db, String owner, Connection conn, DBSQLScript script) {

		if (owner == null || owner.length() == 0)
			throw new InvalidArgumentException("owner", owner);

		EmpireSYSDatabaseOracle sysDB = new EmpireSYSDatabaseOracle(this);
		EmpireDataDictionnaryOracle dataDictionnary = new EmpireDataDictionnaryOracle(
				script, getDDLGeneratorOracle());
		initDataDictionary(db, sysDB, owner, conn, dataDictionnary);
		initConstraintDictionary(db, sysDB, owner, conn, dataDictionnary);
		initConstraintColumns(db, sysDB, owner, conn, dataDictionnary);
		initSequenceDictionary(db, sysDB, owner, conn, dataDictionnary);
		initIndexDictionary(db, sysDB, conn, dataDictionnary);
		initIndexColumns(db, sysDB, owner, conn, dataDictionnary);
		// Check Params
		// Database definition
		// Check Columns

		// check Tables
		dataDictionnary.checkDBTableDefinition(db.getTables());
		// check Views
		dataDictionnary.checkDBViewDefinition(db.getViews());
		// check constraints
		dataDictionnary.checkDBConstraints(db);
		dataDictionnary.checkDBSequences(db);
		dataDictionnary.checkNonUsedObjects(db);
		// done
		log.info("checkDatabase end: " + db.getClass().getName());
		log.info("---------------------------------------------------------------------------------");
	}

	private void initConstraintColumns(DBDatabase db,
			EmpireSYSDatabaseOracle sysDB, String owner, Connection conn,
			EmpireDataDictionnaryOracle constraintDictionnary) {
		DBCommand sysDBCommand = sysDB.createCommand();
		sysDBCommand.select(sysDB.UC.getColumns());
		sysDBCommand.where(sysDB.UC.C_CONSTRAINT_NAME.notLike("SYS_%"));

		DBReader rd = new DBReader();
		try {
			rd.open(sysDBCommand, conn);
			// read all
			log.info("---------------------------------------------------------------------------------");
			log.info("initConstraintDictionary start: "
					+ db.getClass().getName());
			String skipConstraint = "";
			while (rd.moveNext()) {
				String constraintName = rd
						.getString(sysDB.UC.C_CONSTRAINT_NAME);

				// if a table wasn't found before, skip it
				if (constraintName.equals(skipConstraint))
					continue;

				// check if the found table exists in the DBDatabase object
				String columnName = rd.getString(sysDB.UC.C_COLUMN_NAME);
				String tableName = rd.getString(sysDB.UC.C_TABLE_NAME);
				Integer position = rd.getInt(sysDB.UC.C_POSITION);

				constraintDictionnary.fillConstraintColumns(constraintName,
						tableName, columnName, position);
			}
		} finally {
			rd.close();
		}
	}

	private void initIndexColumns(DBDatabase db,
			EmpireSYSDatabaseOracle sysDB, String owner, Connection conn,
			EmpireDataDictionnaryOracle dataDictionnary) {
		DBCommand sysDBCommand = sysDB.createCommand();
		sysDBCommand.select(sysDB.IC.getColumns());
		
		DBReader rd = new DBReader();
		try {
			rd.open(sysDBCommand, conn);
			log.info("---------------------------------------------------------------------------------");
			log.info("initIndexColumns start: "
					+ db.getClass().getName());
			while (rd.moveNext()) {
				String indexName = rd
						.getString(sysDB.IC.C_INDEX_NAME);
				
				String columnName = rd.getString(sysDB.IC.C_COLUMN_NAME);
				String tableName = rd.getString(sysDB.IC.C_TABLE_NAME);
				Integer position = rd.getInt(sysDB.IC.C_COLUMN_POSITION);

				dataDictionnary.fillIndexColumns(indexName, tableName, columnName, position);
			}
		} finally {
			rd.close();
		}
	}

	
	private void initConstraintDictionary(DBDatabase db,
			EmpireSYSDatabaseOracle sysDB, String owner, Connection conn,
			EmpireDataDictionnaryOracle constraintDictionnary) {
		DBCommand sysDBCommand = sysDB.createCommand();
		sysDBCommand.select(sysDB.CO.getColumns());
		sysDBCommand.where(sysDB.CO.C_CONSTRAINT_NAME.notLike("SYS_%"));

		DBReader rd = new DBReader();
		try {
			rd.open(sysDBCommand, conn);
			// read all
			log.info("---------------------------------------------------------------------------------");
			log.info("initConstraintDictionary start: "
					+ db.getClass().getName());
			String skipConstraint = "";
			while (rd.moveNext()) {
				String constraintName = rd
						.getString(sysDB.CO.C_CONSTRAINT_NAME);

				// if a table wasn't found before, skip it
				if (constraintName.equals(skipConstraint))
					continue;

				// check if the found table exists in the DBDatabase object
				String constraintType = rd
						.getString(sysDB.CO.C_CONSTRAINT_TYPE);
				String rConstraintName = rd
						.getString(sysDB.CO.C_R_CONSTRAINT_NAME);
				String status = rd.getString(sysDB.CO.C_STATUS);
				String tableName = rd.getString(sysDB.CO.C_TABLE_NAME);

				constraintDictionnary.fillConstraints(constraintName,
						tableName, rConstraintName, constraintType, status);

			}
		} finally {
			rd.close();
		}
	}

	private void initDataDictionary(DBDatabase db,
			EmpireSYSDatabaseOracle sysDB, String owner, Connection conn,
			EmpireDataDictionnaryOracle dataDictionnary) {
		DBCommand sysDBCommand = sysDB.createCommand();
		sysDBCommand.select(sysDB.CI.getColumns());
		sysDBCommand.where(sysDB.CI.C_OWNER.is(owner));

		DBReader rd = new DBReader();
		try {
			rd.open(sysDBCommand, conn);
			// read all
			log.info("---------------------------------------------------------------------------------");
			log.info("checkDatabase start: " + db.getClass().getName());
			String skipTable = "";
			while (rd.moveNext()) {
				String tableName = rd.getString(sysDB.CI.C_TABLE_NAME);

				// if a table wasn't found before, skip it
				if (tableName.equals(skipTable))
					continue;

				// check if the found table exists in the DBDatabase object
				String columnName = rd.getString(sysDB.CI.C_COLUMN_NAME);
				DBTable dbTable = db.getTable(tableName);
				DBView dbView = db.getView(tableName);

				String dataType = rd.getString(sysDB.CI.C_DATA_TYPE);
				int charLength = rd.getInt(sysDB.CI.C_CHAR_LENGTH);
				int dataLength = rd.getInt(sysDB.CI.C_DATA_LENGTH);
				int dataPrecision = rd.getInt(sysDB.CI.C_DATA_PRECISION);
				int dataScale = rd.getInt(sysDB.CI.C_DATA_SCALE);
				String nullable = rd.getString(sysDB.CI.C_NULLABLE);

				dataDictionnary.fillDataDictionnary(tableName, columnName,
						dataType, charLength, dataLength, dataPrecision,
						dataScale, nullable);

				if (dbTable != null) {

					// check if the found column exists in the found DBTable
					DBColumn col = dbTable.getColumn(columnName);
					if (col == null) {
						log.warn("COLUMN NOT FOUND IN "
								+ db.getClass().getName() + "\t: [" + tableName
								+ "][" + columnName + "][" + dataType + "]["
								+ dataLength + "]");
						continue;
					}
					/*
					 * else { // check the DBTableColumn definition int length =
					 * (charLength>0) ? charLength : dataLength;
					 * dataDictionnary.checkColumnDefinition(col, dataType,
					 * length, dataPrecision, dataScale, nullable.equals("N"));
					 * }
					 */
				} else if (dbView != null) {
					log.debug("Column check for view " + tableName
							+ " not yet implemented.");
				} else {
					log.debug("TABLE OR VIEW NOT FOUND IN "
							+ db.getClass().getName() + "\t: [" + tableName
							+ "]");
					// skip this table
					skipTable = tableName;
					continue;
				}
			}
		} finally {
			rd.close();
		}
	}
	
	private void initIndexDictionary(DBDatabase db,
			EmpireSYSDatabaseOracle sysDB, Connection conn,EmpireDataDictionnaryOracle dataDictionnary) {
		DBCommand sysDBCommand = sysDB.createCommand();
		sysDBCommand.select(sysDB.IX.getColumns());
		sysDBCommand.where(sysDB.IX.C_INDEX_TYPE.is("NORMAL"));
		DBReader rd = new DBReader();
		try {
			rd.open(sysDBCommand, conn);
			log.info("reading indexes");
			while (rd.moveNext()) {
				String indexName=rd.getString(sysDB.IX.C_INDEX_NAME);
				String tableName=rd.getString(sysDB.IX.C_TABLE_NAME);
				String uniqueness = rd.getString(sysDB.IX.C_UNIQUENESS);
				boolean unique=false;
				if ("UNIQUE".equalsIgnoreCase(uniqueness)) {
					unique=true;
				}
				dataDictionnary.fillIndexes(indexName, tableName, unique);				
			}
		} finally {
			rd.close();
		}	
	}

	private void initSequenceDictionary(DBDatabase db,
			EmpireSYSDatabaseOracle sysDB, String owner, Connection conn,
			EmpireDataDictionnaryOracle constraintDictionnary) {
		DBCommand sysDBCommand = sysDB.createCommand();
		sysDBCommand.select(sysDB.SQ.getColumns());
		DBReader rd = new DBReader();
		try {
			rd.open(sysDBCommand, conn);
			// read all
			log.info("---------------------------------------------------------------------------------");
			log.info("initSequenceDictionary start: " + db.getClass().getName());
			while (rd.moveNext()) {
				String sequenceName = rd.getString(sysDB.SQ.C_SEQUENCE_NAME);
				constraintDictionnary.fillSequences(sequenceName);
			}
		} finally {
			rd.close();
		}
	}

}
